﻿using OnDash.Mathematics.Data;
using OnDash.Mathematics.Matrix;

namespace OnDash.Mathematics.Vector;

public struct Vector4(float x, float y, float z, float w)
{
    public float X = x;

    public float Y = y;

    public float Z = z;

    public float W = w;

    public Vector4(float value) : this(value, value, value, value)
    {
    }

    public Vector4(Vector2 value) : this(value.X, value.Y, 0, 0)
    {
    }

    public Vector4(Vector3 value) : this(value.X, value.Y, value.Z, 0)
    {
    }

    public Vector4(Vector3 value, float w) : this(value.X, value.Y, value.Z, w)
    {
    }

    public Vector4(Vector4 value) : this(value.X, value.Y, value.Z, value.W)
    {
    }

    public Vector2 Xy
    {
        get => new(X, Y);
        set
        {
            X = value.X;
            Y = value.Y;
        }
    }

    public Vector2 Xz
    {
        get => new(X, Z);
        set
        {
            X = value.X;
            Z = value.Y;
        }
    }

    public Vector2 Xw
    {
        get => new(X, W);
        set
        {
            X = value.X;
            W = value.Y;
        }
    }

    public Vector2 Yx
    {
        get => new(Y, X);
        set
        {
            Y = value.X;
            X = value.Y;
        }
    }

    public Vector2 Yz
    {
        get => new(Y, Z);
        set
        {
            Y = value.X;
            Z = value.Y;
        }
    }

    public Vector2 Yw
    {
        get => new(Y, W);
        set
        {
            Y = value.X;
            W = value.Y;
        }
    }

    public Vector2 Zx
    {
        get => new(Z, X);
        set
        {
            Z = value.X;
            X = value.Y;
        }
    }

    public Vector2 Zy
    {
        get => new(Z, Y);
        set
        {
            Z = value.X;
            Y = value.Y;
        }
    }

    public Vector2 Zw
    {
        get => new(Z, W);
        set
        {
            Z = value.X;
            W = value.Y;
        }
    }

    public Vector2 Wx
    {
        get => new(W, X);
        set
        {
            W = value.X;
            X = value.Y;
        }
    }

    public Vector2 Wy
    {
        get => new(W, Y);
        set
        {
            W = value.X;
            Y = value.Y;
        }
    }

    public Vector2 Wz
    {
        get => new(W, Z);
        set
        {
            W = value.X;
            Z = value.Y;
        }
    }

    public Vector3 Xyz
    {
        get => new(X, Y, Z);
        set
        {
            X = value.X;
            Y = value.Y;
            Z = value.Z;
        }
    }

    public Vector3 Xyw
    {
        get => new(X, Y, W);
        set
        {
            X = value.X;
            Y = value.Y;
            W = value.Z;
        }
    }

    public Vector3 Xzy
    {
        get => new(X, Z, Y);
        set
        {
            X = value.X;
            Z = value.Y;
            Y = value.Z;
        }
    }

    public Vector3 Xzw
    {
        get => new(X, Z, W);
        set
        {
            X = value.X;
            Z = value.Y;
            W = value.Z;
        }
    }

    public Vector3 Xwy
    {
        get => new(X, W, Y);
        set
        {
            X = value.X;
            W = value.Y;
            Y = value.Z;
        }
    }

    public Vector3 Xwz
    {
        get => new(X, W, Z);
        set
        {
            X = value.X;
            W = value.Y;
            Z = value.Z;
        }
    }

    public Vector3 Yxz
    {
        get => new(Y, X, Z);
        set
        {
            Y = value.X;
            X = value.Y;
            Z = value.Z;
        }
    }

    public Vector3 Yxw
    {
        get => new(Y, X, W);
        set
        {
            Y = value.X;
            X = value.Y;
            W = value.Z;
        }
    }

    public Vector3 Yzx
    {
        get => new(Y, Z, X);
        set
        {
            Y = value.X;
            Z = value.Y;
            X = value.Z;
        }
    }

    public Vector3 Yzw
    {
        get => new(Y, Z, W);
        set
        {
            Y = value.X;
            Z = value.Y;
            W = value.Z;
        }
    }

    public Vector3 Ywx
    {
        get => new(Y, W, X);
        set
        {
            Y = value.X;
            W = value.Y;
            X = value.Z;
        }
    }

    public Vector3 Ywz
    {
        get => new(Y, W, Z);
        set
        {
            Y = value.X;
            W = value.Y;
            Z = value.Z;
        }
    }

    public Vector3 Zxy
    {
        get => new(Z, X, Y);
        set
        {
            Z = value.X;
            X = value.Y;
            Y = value.Z;
        }
    }

    public Vector3 Zxw
    {
        get => new(Z, X, W);
        set
        {
            Z = value.X;
            X = value.Y;
            W = value.Z;
        }
    }

    public Vector3 Zyx
    {
        get => new(Z, Y, X);
        set
        {
            Z = value.X;
            Y = value.Y;
            X = value.Z;
        }
    }

    public Vector3 Zyw
    {
        get => new(Z, Y, W);
        set
        {
            Z = value.X;
            Y = value.Y;
            W = value.Z;
        }
    }

    public Vector3 Zwx
    {
        get => new(Z, W, X);
        set
        {
            Z = value.X;
            W = value.Y;
            X = value.Z;
        }
    }

    public Vector3 Zwy
    {
        get => new(Z, W, Y);
        set
        {
            Z = value.X;
            W = value.Y;
            Y = value.Z;
        }
    }

    public Vector3 Wxy
    {
        get => new(W, X, Y);
        set
        {
            W = value.X;
            X = value.Y;
            Y = value.Z;
        }
    }

    public Vector3 Wxz
    {
        get => new(W, X, Z);
        set
        {
            W = value.X;
            X = value.Y;
            Z = value.Z;
        }
    }

    public Vector3 Wyx
    {
        get => new(W, Y, X);
        set
        {
            W = value.X;
            Y = value.Y;
            X = value.Z;
        }
    }

    public Vector3 Wyz
    {
        get => new(W, Y, Z);
        set
        {
            W = value.X;
            Y = value.Y;
            Z = value.Z;
        }
    }

    public Vector3 Wzx
    {
        get => new(W, Z, X);
        set
        {
            W = value.X;
            Z = value.Y;
            X = value.Z;
        }
    }

    public Vector3 Wzy
    {
        get => new(W, Z, Y);
        set
        {
            W = value.X;
            Z = value.Y;
            Y = value.Z;
        }
    }

    public Vector4 Xyzw
    {
        get => new(X, Y, Z, W);
        set
        {
            X = value.X;
            Y = value.Y;
            Z = value.Z;
            W = value.W;
        }
    }

    public Vector4 Xywz
    {
        get => new(X, Y, W, Z);
        set
        {
            X = value.X;
            Y = value.Y;
            W = value.Z;
            Z = value.W;
        }
    }

    public Vector4 Xzyw
    {
        get => new(X, Z, Y, W);
        set
        {
            X = value.X;
            Z = value.Y;
            Y = value.Z;
            W = value.W;
        }
    }

    public Vector4 Xzwy
    {
        get => new(X, Z, W, Y);
        set
        {
            X = value.X;
            Z = value.Y;
            W = value.Z;
            Y = value.W;
        }
    }

    public Vector4 Xwyz
    {
        get => new(X, W, Y, Z);
        set
        {
            X = value.X;
            W = value.Y;
            Y = value.Z;
            Z = value.W;
        }
    }

    public Vector4 Xwzy
    {
        get => new(X, W, Y, Z);
        set
        {
            X = value.X;
            W = value.Y;
            Y = value.Z;
            Z = value.W;
        }
    }

    public Vector4 Yxzw
    {
        get => new(Y, X, Z, W);
        set
        {
            Y = value.X;
            X = value.Y;
            Z = value.Z;
            W = value.W;
        }
    }

    public Vector4 Yxwz
    {
        get => new(Y, X, W, Z);
        set
        {
            Y = value.X;
            X = value.Y;
            W = value.Z;
            Z = value.W;
        }
    }

    public Vector4 Yzxw
    {
        get => new(Y, Z, X, W);
        set
        {
            Y = value.X;
            Z = value.Y;
            X = value.Z;
            W = value.W;
        }
    }

    public Vector4 Yzwx
    {
        get => new(Y, Z, W, X);
        set
        {
            Y = value.X;
            Z = value.Y;
            W = value.Z;
            X = value.W;
        }
    }

    public Vector4 Ywxz
    {
        get => new(Y, W, X, Z);
        set
        {
            Y = value.X;
            W = value.Y;
            X = value.Z;
            Z = value.W;
        }
    }

    public Vector4 Ywzx
    {
        get => new(Y, W, Z, X);
        set
        {
            Y = value.X;
            W = value.Y;
            Z = value.Z;
            X = value.W;
        }
    }

    public Vector4 Zxyw
    {
        get => new(Z, X, Y, W);
        set
        {
            Z = value.X;
            X = value.Y;
            Y = value.Z;
            W = value.W;
        }
    }

    public Vector4 Zxwy
    {
        get => new(Z, X, W, Y);
        set
        {
            Z = value.X;
            X = value.Y;
            W = value.Z;
            Y = value.W;
        }
    }

    public Vector4 Zyxw
    {
        get => new(Z, Y, X, W);
        set
        {
            Z = value.X;
            Y = value.Y;
            X = value.Z;
            W = value.W;
        }
    }

    public Vector4 Zywx
    {
        get => new(Z, Y, W, X);
        set
        {
            Z = value.X;
            Y = value.Y;
            W = value.Z;
            X = value.W;
        }
    }

    public Vector4 Zwxy
    {
        get => new(Z, W, X, Y);
        set
        {
            Z = value.X;
            W = value.Y;
            X = value.Z;
            Y = value.W;
        }
    }

    public Vector4 Zwyx
    {
        get => new(Z, W, Y, X);
        set
        {
            Z = value.X;
            W = value.Y;
            Y = value.Z;
            X = value.W;
        }
    }

    public Vector4 Wxyz
    {
        get => new(W, X, Y, Z);
        set
        {
            W = value.X;
            X = value.Y;
            Y = value.Z;
            Z = value.W;
        }
    }

    public Vector4 Wxzy
    {
        get => new(W, X, Z, Y);
        set
        {
            W = value.X;
            X = value.Y;
            Z = value.Z;
            Y = value.W;
        }
    }

    public Vector4 Wzxy
    {
        get => new(W, Z, X, Y);
        set
        {
            W = value.X;
            Z = value.Y;
            X = value.Z;
            Y = value.W;
        }
    }

    public Vector4 Wzyx
    {
        get => new(W, Z, Y, X);
        set
        {
            W = value.X;
            Z = value.Y;
            Y = value.Z;
            X = value.W;
        }
    }

    public Vector4 Wyzx
    {
        get => new(W, Y, Z, X);
        set
        {
            W = value.X;
            Y = value.Y;
            Z = value.Z;
            X = value.W;
        }
    }

    public Vector4 Wyxz
    {
        get => new(W, Y, X, Z);
        set
        {
            W = value.X;
            Y = value.Y;
            X = value.Z;
            Z = value.W;
        }
    }

    public float this[int index]
    {
        get
        {
            return index switch
            {
                0 => X,
                1 => Y,
                2 => Z,
                3 => W,
                _ => throw new IndexOutOfRangeException($"{index} is out of range.")
            };
        }
        set
        {
            switch (index)
            {
                case 0:
                    X = value;
                    break;
                case 1:
                    Y = value;
                    break;
                case 2:
                    Z = value;
                    break;
                case 3:
                    W = value;
                    break;
                default:
                    throw new IndexOutOfRangeException($"{index} is out of range.");
            }
        }
    }

    public float Length
        => MathHelper.Sqrt(X * X + Y * Y + Z * Z + W * W);

    public float LengthSquared
        => X * X + Y * Y + Z * Z + W * W;

    public void Normalize()
    {
        var scale = MathHelper.InverseSqrtFast(LengthSquared);
        X *= scale;
        Y *= scale;
        Z *= scale;
        W *= scale;
    }

    public Vector4 Normalized()
    {
        var vector = this;
        vector.Normalize();
        return vector;
    }

    public static Vector4 UnitX = new(1, 0, 0, 0);
    public static Vector4 UnitY = new(0, 1, 0, 0);
    public static Vector4 UnitZ = new(0, 0, 1, 0);
    public static Vector4 UnitW = new(0, 0, 0, 1);

    public static Vector4 Zero = new(0, 0, 0, 0);
    public static Vector4 One = new(1, 1, 1, 1);

    public static Vector4 Add(Vector4 left, Vector4 right)
        => new(left.X + right.X, left.Y + right.Y, left.Z + right.Z, left.W + right.W);

    public static Vector4 Subtract(Vector4 left, Vector4 right)
        => new(left.X - right.X, left.Y - right.Y, left.Z - right.Z, left.W - right.W);

    public static Vector4 Multiply(Vector4 left, float right)
        => new(left.X * right, left.Y * right, left.Z * right, left.W * right);

    public static Vector4 Multiply(Vector4 left, Vector4 right)
        => new(left.X * right.X, left.Y * right.Y, left.Z * right.Z, left.W * right.W);

    public static Vector4 Divide(Vector4 left, float right)
        => new(left.X / right, left.Y / right, left.Z / right, left.W / right);

    public static Vector4 Divide(Vector4 left, Vector4 right)
        => new(left.X / right.X, left.Y / right.Y, left.Z / right.Z, left.W / right.W);

    public static Vector4 Min(Vector4 left, Vector4 right)
        => left.LengthSquared < right.LengthSquared ? left : right;

    public static Vector4 Max(Vector4 left, Vector4 right)
        => left.LengthSquared >= right.LengthSquared ? left : right;

    public static Vector4 ComponentMin(Vector4 left, Vector4 right)
    {
        return new Vector4(
            left.X < right.X ? left.X : right.X,
            left.Y < right.Y ? left.Y : right.Y,
            left.Z < right.Z ? left.Z : right.Z,
            left.W < right.W ? left.W : right.W
        );
    }

    public static Vector4 ComponentMax(Vector4 left, Vector4 right)
    {
        return new Vector4(
            left.X > right.X ? left.X : right.X,
            left.Y > right.Y ? left.Y : right.Y,
            left.Z > right.Z ? left.Z : right.Z,
            left.W > right.W ? left.W : right.W
        );
    }

    public static Vector4 MagnitudeMin(Vector4 left, Vector4 right)
        => left.LengthSquared < right.LengthSquared ? left : right;

    public static Vector4 MagnitudeMax(Vector4 left, Vector4 right)
        => left.LengthSquared >= right.LengthSquared ? left : right;

    public static Vector4 Clamp(Vector4 vector, Vector4 min, Vector4 max)
    {
        return new Vector4(
            vector.X < min.X ? min.X : vector.X > max.X ? max.X : vector.X,
            vector.Y < min.Y ? min.Y : vector.Y > max.Y ? max.Y : vector.Y,
            vector.Z < min.Z ? min.Z : vector.Z > max.Z ? max.Z : vector.Z,
            vector.W < min.W ? min.W : vector.W > max.W ? max.W : vector.W
        );
    }

    public static float Dot(Vector4 left, Vector4 right)
        => left.X * right.X + left.Y * right.Y + left.Z * right.Z + left.W * right.W;

    public static Vector4 Lerp(Vector4 left, Vector4 right, float blend)
    {
        return new Vector4(
            left.X + blend * (right.X - left.X),
            left.Y + blend * (right.Y - left.Y),
            left.Z + blend * (right.Z - left.Z),
            left.W + blend * (right.W - left.W)
        );
    }

    public static Vector4 Negate(Vector4 vector)
        => new(-vector.X, -vector.Y, -vector.Z, -vector.W);

    public static Vector4 BaryCentric(Vector4 a, Vector4 b, Vector4 c, float u, float v)
        => a + u * (b - a) + v * (c - a);

    public static Vector4 Transform(Vector4 vector, Matrix4 matrix)
    {
        return new Vector4(
            vector.X * matrix.M11 + vector.Y * matrix.M21 + vector.Z * matrix.M31 + vector.W * matrix.M41,
            vector.X * matrix.M12 + vector.Y * matrix.M22 + vector.Z * matrix.M32 + vector.W * matrix.M42,
            vector.X * matrix.M13 + vector.Y * matrix.M23 + vector.Z * matrix.M33 + vector.W * matrix.M43,
            vector.X * matrix.M14 + vector.Y * matrix.M24 + vector.Z * matrix.M34 + vector.W * matrix.M44
        );
    }

    public static Vector4 Transform(Vector4 vector, Quaternion quaternion)
    {
        var v = new Quaternion(vector.X, vector.Y, vector.Z, vector.W);
        var i = Quaternion.Invert(quaternion);
        var t = Quaternion.Multiply(quaternion, v);
        Quaternion.Multiply(t, i);

        return new Vector4(vector.X, vector.Y, vector.Z, vector.W);
    }

    public static Vector4 Transform(Matrix4 matrix, Vector4 vector)
    {
        return new Vector4
        (
            matrix.M11 * vector.X + matrix.M12 * vector.Y + matrix.M13 * vector.Z + matrix.M14 * vector.W,
            matrix.M21 * vector.X + matrix.M22 * vector.Y + matrix.M23 * vector.Z + matrix.M24 * vector.W,
            matrix.M31 * vector.X + matrix.M32 * vector.Y + matrix.M33 * vector.Z + matrix.M34 * vector.W,
            matrix.M41 * vector.X + matrix.M42 * vector.Y + matrix.M43 * vector.Z + matrix.M44 * vector.W
        );
    }

    public static Vector4 operator /(Vector4 vector, float scale)
    {
        return new Vector4
        (
            vector.X / scale,
            vector.Y / scale,
            vector.Z / scale,
            vector.W / scale
        );
    }

    public static Vector4 operator +(Vector4 left, Vector4 right) => Add(left, right);
    public static Vector4 operator -(Vector4 left, Vector4 right) => Subtract(left, right);
    public static Vector4 operator -(Vector4 vector) => Negate(vector);
    public static Vector4 operator *(Vector4 vector, float scale) => Multiply(vector, scale);
    public static Vector4 operator *(float scale, Vector4 vector) => Multiply(vector, scale);
    public static Vector4 operator *(Vector4 left, Vector4 right) => Multiply(left, right);
    public static Vector4 operator *(Vector4 left, Matrix4 matrix) => Transform(left, matrix);
    public static Vector4 operator *(Matrix4 matrix, Vector4 vector) => Transform(vector, matrix);
    public static Vector4 operator *(Quaternion quaternion, Vector4 vector) => Transform(vector, quaternion);
    public static Vector4 operator /(Vector4 left, Vector4 right) => Divide(left, right);

    public static bool operator ==(Vector4 left, Vector4 right) => left.Equals(right);
    public static bool operator !=(Vector4 left, Vector4 right) => !left.Equals(right);

    public override string ToString()
        => string.Format("({0}{4}, {1}{4}, {2}{4}, {3})", X, Y, Z, W, MathHelper.ListSeparator);

    public override int GetHashCode()
        => HashCode.Combine(X, Y, Z, W);

    public override bool Equals(object? obj)
        => obj is Vector4 vector && Equals(vector);

    public bool Equals(Vector4 other)
        => X.Equals(other.X) &&
           Y.Equals(other.Y) &&
           Z.Equals(other.Z) &&
           W.Equals(other.W);
}